#include <sxOpenGL_Canvas.h>
#include <sxOpenGL_Context.h>
#include <sxMath.h>
#include <gl/glut.h>

Sx::OpenGL::Interface::OpenGL_Canvas::OpenGL_Canvas(const std::shared_ptr<OpenGL_Context>& context) {
	if ( context == nullptr ) {
		std::cerr << "[OpenGL_Canvas:OpenGL_Canvas] Error: Cannot create OpenGL canvas with nullptr context." << std::endl;
	}

	this->context = context;
	this->enableDepthTest = true;
}

Sx::OpenGL::Interface::OpenGL_Canvas::~OpenGL_Canvas() {
	/* this->canvas not owned by this object */
}

bool Sx::OpenGL::Interface::OpenGL_Canvas::initialize() {
	if ( this->context == nullptr ) {
		std::cerr << "[OpenGL_Canvas:initialize] Error: Cannot initialize nullptr context." << std::endl;
		return false;
	}

	if ( this->context->isInitialized() ) this->context->initialize();
	return true;
}

/* 
 * OpenGL canvas does not inherently have any components that need to be
 * rendered  by default.
 */
void Sx::OpenGL::Interface::OpenGL_Canvas::render() const { return; }

/* OpenGL canvas managed by Camera renderer implementations */
void Sx::OpenGL::Interface::OpenGL_Canvas::resize(int width, int height) { return; }

/* Function utilizes immediate OpenGL rendering (depreciated) */
bool Sx::OpenGL::Interface::OpenGL_Canvas::drawCircle(float radius, int xCenter, int yCenter, const Color4f& color, unsigned int segmentCount) const {
	if ( this->context == nullptr ) {
		std::cerr << "[OpenGL_Canvas:drawCircle] Error: Cannot draw to nullptr OpenGL context." << std::endl;
		return false;
	}

	if ( radius <= 0.0f ) {
		std::cout << "[OpenGL_Canvas:drawCircle] Error: Cannot draw circle with radius 0." << std::endl;
		return false;
	}

	glDisable(GL_TEXTURE_2D);
	glDisable(GL_LIGHTING);

	float c = Math::TWO_PI;
	float angle = c / (float)segmentCount;
	float currentAngle = 0.0f;
	float startX = (radius * cos(angle)) - (radius * sin(angle));
	float startY = (radius * cos(angle)) + (radius * sin(angle));

	if ( this->enableDepthTest ) glEnable(GL_DEPTH_TEST);
	else glDisable(GL_DEPTH_TEST);

	glMatrixMode(GL_PROJECTION);
		glPushMatrix();
		glLoadIdentity();
		int viewport[4];
		glGetIntegerv(GL_VIEWPORT, viewport);
		glOrtho(0.0, viewport[2], viewport[3], 0.0f, 0.0, 1.0); 
		glMatrixMode(GL_MODELVIEW);
		glPushMatrix();
			glLoadIdentity();
			glRasterPos3f(xCenter, yCenter, 1.0f);
			glColor3f(color.getR(), color.getG(), color.getB());
			glBegin(GL_LINE_LOOP);

			float x = 0.0f, y = 0.0f, z = 0.0f;
			for ( unsigned int i = 0; i <= segmentCount; i++ ) {

				x = (radius * cos(currentAngle)) - (radius * sin(currentAngle));
				y = (radius * cos(currentAngle)) + (radius * sin(currentAngle));

				glVertex2f(xCenter + x, yCenter + y);
				currentAngle += angle;
			}

			glEnd();
		glPopMatrix();
		glMatrixMode(GL_PROJECTION);
		glPopMatrix();
	glMatrixMode(GL_MODELVIEW);

	glEnable(GL_DEPTH_TEST);
	glEnable(GL_LIGHTING);
	glEnable(GL_TEXTURE_2D);
	return true;
}

bool Sx::OpenGL::Interface::OpenGL_Canvas::drawText(const String& text, int x, int y, const Color4f& color) const {
	if ( this->context == nullptr ) {
		std::cerr << "[OpenGL_Canvas:drawText] Error: Cannot draw to nullptr OpenGL context." << std::endl;
		return false;
	}

	glDisable(GL_TEXTURE_2D);
	glDisable(GL_LIGHTING);
	glMatrixMode(GL_PROJECTION);
		glPushMatrix();
		glLoadIdentity();
		int viewport[4];
		glGetIntegerv(GL_VIEWPORT, viewport);
		glOrtho(0.0, viewport[2], viewport[3], 0.0f, -1.0f, 1.0f); 
		glMatrixMode(GL_MODELVIEW);
		glPushMatrix();
			glLoadIdentity();
			glColor4fv(color);
			glRasterPos3f(x, y, 1.0f);
			for ( unsigned int i = 0; i < text.length(); i++ )
				glutBitmapCharacter(GLUT_BITMAP_HELVETICA_12, text.at(i));
		glPopMatrix();
		glMatrixMode(GL_PROJECTION);
		glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
	return true;
}

bool Sx::OpenGL::Interface::OpenGL_Canvas::draw3DText(const String& text, float x, float y, float z, const Color4f& color) const {
	if ( this->context == nullptr ) {
		std::cerr << "[OpenGL_Canvas:draw3DText] Error: Cannot draw to nullptr OpenGL context." << std::endl;
		return false;
	}

	if ( this->enableDepthTest ) glEnable(GL_DEPTH_TEST);
	else glDisable(GL_DEPTH_TEST);

	glDisable(GL_TEXTURE_2D);
	glDisable(GL_LIGHTING);

	glColor4fv(color);
	glRasterPos3f(x, y, z);
	for ( unsigned int i = 0; i < text.length(); i++ ) glutBitmapCharacter(GLUT_BITMAP_HELVETICA_12, text.at(i));

	glEnable(GL_TEXTURE_2D);
	glEnable(GL_LIGHTING);
	return true;
}

bool Sx::OpenGL::Interface::OpenGL_Canvas::drawBorder(int x, int y, int width, int height, unsigned int borderWidth, const Color4f& color) const {
	if ( this->context == nullptr ) {
		std::cerr << "[OpenGL_Canvas:drawBorder] Error: Cannot draw to nullptr OpenGL context." << std::endl;
		return false;
	}

	glDisable(GL_TEXTURE_2D);
	glDisable(GL_LIGHTING);

	glMatrixMode(GL_PROJECTION);
		glPushMatrix();
		glLoadIdentity();
		int viewport[4];
		glGetIntegerv(GL_VIEWPORT, viewport);

		glOrtho(0.0, viewport[2], 0.0f, viewport[3], -1.0, 1.0); 
		glMatrixMode(GL_MODELVIEW);
		glPushMatrix();
			glLoadIdentity();

			glColor3f(color.getR(), color.getG(), color.getB());	
			glLineWidth(borderWidth);
			float halfWidth = borderWidth / 2.0f;
			glBegin(GL_LINES);
				glVertex3f(halfWidth, 0.0f, halfWidth);
				glVertex3f(halfWidth, (float)viewport[3], halfWidth);

				glVertex3f(halfWidth, halfWidth, halfWidth);
				glVertex3f((float)viewport[2], halfWidth, halfWidth);

				glVertex3f((float)viewport[2] - halfWidth, halfWidth, halfWidth);
				glVertex3f((float)viewport[2] - halfWidth, (float)viewport[3] - halfWidth, halfWidth);

				glVertex3f(halfWidth, (float)viewport[3] - halfWidth, halfWidth);
				glVertex3f((float)viewport[2], (float)viewport[3] - halfWidth, halfWidth);
			glEnd();
		glPopMatrix();
		glMatrixMode(GL_PROJECTION);
		glPopMatrix();
		glLineWidth(1.0f);
	glMatrixMode(GL_MODELVIEW);

	return true;
}

bool Sx::OpenGL::Interface::OpenGL_Canvas::drawRectangle(int x, int y, int width, int height, const Color4f& color, bool drawFromCenter) const {
	if ( this->context == nullptr ) {
		std::cerr << "[OpenGL_Canvas:drawCircle] Error: Cannot draw to nullptr OpenGL context." << std::endl;
		return false;
	}

	if ( color.getA() != 1.0f ) {
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		glEnable( GL_BLEND );
	}

	if ( this->enableDepthTest ) glEnable(GL_DEPTH_TEST);
	else glDisable(GL_DEPTH_TEST);

	glDisable(GL_TEXTURE_2D);
	glDisable(GL_LIGHTING);

	glMatrixMode(GL_PROJECTION);
		glPushMatrix();
		glLoadIdentity();
		int viewport[4];
		glGetIntegerv(GL_VIEWPORT, viewport);
		glOrtho(0.0, viewport[2], viewport[3], 0.0f, 0.0, 1.0); 
		glMatrixMode(GL_MODELVIEW);
		glPushMatrix();
			glLoadIdentity();
			glColor4fv(color);
			glRasterPos3f(x, y, 0.01f);
			glBegin(GL_LINES);
				if ( drawFromCenter == false ) {
					// Top
					glVertex2i(x, y);
					glVertex2i(x + width, y);
					// Left
					glVertex2i(x, y);
					glVertex2i(x, y + height);
					// Right
					glVertex2i(x + width, y);
					glVertex2i(x + width, y + height);
					// Bottom
					glVertex2i(x, y + height);
					glVertex2i(x + width, y + height);
				}
				else {
					// Top
					glVertex2i(x - (width / 2.0), y - (height / 2.0));
					glVertex2i(x + (width / 2.0), y - (height / 2.0));
					// Left
					glVertex2i(x - (width / 2.0), y - (height / 2.0));
					glVertex2i(x - (width / 2.0), y + (height / 2.0));
					// Right
					glVertex2i(x + (width / 2.0), y - (height / 2.0));
					glVertex2i(x + (width / 2.0), y + (height / 2.0));
					// Bottom
					glVertex2i(x - (width / 2.0), y + (height / 2.0));
					glVertex2i(x + (width / 2.0), y + (height / 2.0));
				}
			glEnd();
			
		glPopMatrix();
		glMatrixMode(GL_PROJECTION);
		glPopMatrix();
	glMatrixMode(GL_MODELVIEW);

	glEnable(GL_TEXTURE_2D);
	glEnable(GL_LIGHTING);
	if ( color.getA() != 1.0f ) glDisable( GL_BLEND );
	return true;
}

bool Sx::OpenGL::Interface::OpenGL_Canvas::fillRectangle(int x, int y, int width, int height, const Color4f& color, bool drawFromCenter) const {
	if ( this->context == nullptr ) {
		std::cerr << "[OpenGL_Canvas:fillRectangle] Error: Cannot draw to nullptr OpenGL context." << std::endl;
		return false;
	}
	
	if ( color.getA() != 1.0f ) {
		glDisable(GL_DEPTH_TEST);
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		glDisable(GL_CULL_FACE);
		glShadeModel (GL_FLAT);
		glDepthFunc(GL_LEQUAL);
	}

	if ( this->enableDepthTest ) glEnable(GL_DEPTH_TEST);
	else glDisable(GL_DEPTH_TEST);

	glDisable(GL_TEXTURE_2D);
	glDisable(GL_LIGHTING);

	glMatrixMode(GL_PROJECTION);
		glPushMatrix();
		glLoadIdentity();
		int viewport[4];
		glGetIntegerv(GL_VIEWPORT, viewport);
		glOrtho(0.0, viewport[2], viewport[3], 0.0f, 0.0, 1.0); 
		glMatrixMode(GL_MODELVIEW);
		glPushMatrix();
			glLoadIdentity();
			glColor4fv(color);
			glRasterPos3f(x, y, 0.0f);
			glBegin(GL_TRIANGLE_STRIP);
				if ( drawFromCenter == false ) {
					glVertex2i(x, y);
					glVertex2i(x, y + height);
					glVertex2i(x + width, y);
					glVertex2i(x + width, y + height);
				}
				else {
					glVertex2i(x - (width / 2.0), y - (height / 2.0));
					glVertex2i(x - (width / 2.0), y + (height / 2.0));
					glVertex2i(x + (width / 2.0), y - (height / 2.0));
					glVertex2i(x + (width / 2.0), y + (height / 2.0));
				}
			glEnd();
			
		glPopMatrix();
		glMatrixMode(GL_PROJECTION);
		glPopMatrix();
	glMatrixMode(GL_MODELVIEW);

	glEnable(GL_TEXTURE_2D);
	glEnable(GL_LIGHTING);
	if ( color.getA() != 1.0f ) {
		glDisable(GL_BLEND);
		glEnable(GL_CULL_FACE);
		glEnable(GL_DEPTH_TEST);
	}

	return true;
}
